package com.solarStruggle.graphics

import java.awt.{Color, Point}

/**
 * MetaBall seeding function.  MetaBalls are gooey balls that stretch together.
 * We start with a similar equation to the distance equation:
 *
 * <code>strength = a - ((x - nx) ^ 2 + (y - ny) ^2) / b</code>
 *
 * <p>Here, (x, y) is the point being evaluated, and nx and ny are the
 * coordinates for the given blob.  This formula is calculated for each blob
 * and the results summed together.</p>
 *
 * <p>a and b are constants chosen so that the values are interpolated between
 * ri (the inner radius), and ro (the outer radius). Everything inside of ri 
 * should have a value of 1.0, and everything outside of ro should have a value 
 * of 0.0.  The values should decrease smoothly between these two points.</p>
 *
 * <p>To do this, we take the strength equation (let's call it f(x)), and
 * take the follow constraints as given:</p>
 *
 * <code>f(nx - ri) = 1</code>
 * <code>f(nx - ro) = z</code>
 *
 * <p>Where z is some constant close to zero, e.g. .01</p>
 *
 * <p>First, substitute the original equation back in:</p>
 *
 * <code>a - ((nx - ri - nx) ^ 2) + (ny - ri - ny) ^ 2) / b = 1</code>
 * <code>a - ((nx - ro - nx) ^ 2) + (ny - ro - ny) ^ 2) / b = z</code>
 *
 * <p>Meaning</p>
 *
 * <code> a - ((-ri) ^ 2 + ri ^ 2) / b = 1</code>
 * <code> a - ((-ro) ^ 2 + ro ^ 2) / b = z</code>
 *
 * <p>i.e.</p>
 *
 * <code>a - 2 * ri ^ 2 / b = 1</code>
 * <code>a - 2 * ro ^ 2 / b = z</code>
 *
 * <p>The inner equation consists entirely of known quantities, so we can
 * subsitute a constant for it now, call it n1 and n2:</p>
 *
 * <code>a - n1 / b = 1</code>
 * <code>a - n2 / b = z</code>
 *
 * <p>Or, equivalently</p>
 *
 * <code>a - n1 / b - 1 = 0</code>
 * <code>a - n2 / b - z = 0</code>
 *
 * <code>a - n1 / b - 1 = a - n2 / b - z</code>
 * <code>a - n1 / b - 1 - (a - n2 / b - z) = 0</code>
 * <code>a - n1 / b - 1- a + n2 / b + z = 0</code>
 * <code>-n1 / b - 1 + n2 / b + z = 0</code>
 * <code>(n2 - n1) / b - 1 + z = 0</code>
 * <code>(n2 - n1) / b = 1 - z</code>
 * <code>b = (n2 - n1) / (1 - z)</code>
 *
 * <p>Solving the original equation for a yields</p>
 *
 * <code>a = 1 + n1 / b</code>
 */
abstract class MetaBallGenerator(width: Int, height: Int, fill: Color, squareSize: Int, ri: Int, ro: Int, balls: Seq[Point])
extends ProceduralImageGenerator(width, height, fill, squareSize) {
  
  def colorFunction(value: Float): Color
  
  override def renderPixel(image: BufferedImage, x: Int, y: Int) {
    val z = .01
    val n1 = 2.0 * Math.pow(ri.toDouble, 2.0)
    val n2 = 2.0 * Math.pow(ro.toDouble, 2.0)
    val b = (n2 - n1 / (1.0 - z))
    val a = 1 + n1 / b
    
    def trimRange(n: Double): Float = n match {
      case x if x >= 1.0 => 1.0f
      case x if x <= z   => 0.0f
      case x             => x.toFloat
    }
    
    def f(x: Double, y: Double, n: Point): Double = {
      trimRange(a - (Math.pow(x - n.getX, 2.0) + Math.pow(y - n.getY, 2.0)) / b);
    }
    
    image.setPixel(x, y, 
      colorFunction(
        balls.foldLeft(0.0f) { 
          (sum, point) => trimRange(sum + (1 - sum) * f(x, y, point)) 
        }))
  }
}
